package utils
{
	
	import com.adobe.utils.*;
	
	import flash.display.Stage3D;
	import flash.display3D.*;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.*;
	import flash.geom.Matrix;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	
	public class Molehill_entity
	{
		// Matrix variables (position, rotation, etc.)
		private var _transform:Matrix3D;
		private var _inverseTransform:Matrix3D;
		private var _transformNeedsUpdate:Boolean;
		private var _valuesNeedUpdate:Boolean;
		private var _x:Number = 0;
		private var _y:Number = 0;
		private var _z:Number = 0;
		private var _rotationDegreesX:Number = 0;
		private var _rotationDegreesY:Number = 0;
		private var _rotationDegreesZ:Number = 0;
		private var _scaleX:Number = 1;
		private var _scaleY:Number = 1;
		private var _scaleZ:Number = 1;
		private const RAD_TO_DEG:Number = 180/Math.PI;
		// Molehill objects
		public var context:Context3D;
		public var vertexBuffer:VertexBuffer3D;
		public var indexBuffer:IndexBuffer3D;
		public var shader:Program3D;
		public var texture:Texture;
		public var mesh:Molehill_obj_parser;
		// Render modes:
		public var blend_src:String = Context3DBlendFactor.ONE;
		public var blend_dst:String = Context3DBlendFactor.ZERO;
		public var depth_test_mode:String = Context3DCompareMode.LESS;
		public var depth_test:Boolean = true;
		public var culling_mode:String = Context3DTriangleFace.FRONT;
		// if this is set entity is "stuck" to another
		public var _following:Molehill_entity;
		
		// used only for stats
		public var polycount:uint = 0;
		
		public function Molehill_entity(mydata:Class = null,mycontext:Context3D = null,
										myshader:Program3D = null,mytexture:Texture = null,modelscale:Number = 1,
										data_is_zxy:Boolean = true, texture_flip:Boolean = true)
		{
			_transform = new Matrix3D();
			context = mycontext;
			shader = myshader;
			texture = mytexture;
			if (mydata)
				mesh = new Molehill_obj_parser(mydata, context, modelscale, data_is_zxy, texture_flip);
			//polycount = mesh.indexBufferCount;
			trace("Mesh has " + polycount + " polygons.");
		}
		
		public function get transform():Matrix3D
		{
			if(_transformNeedsUpdate)
				updateTransformFromValues();
			return _transform;
		}
		public function set transform(value:Matrix3D):void
		{
			_transform = value;
			_transformNeedsUpdate = false;
			_valuesNeedUpdate = true;
		}
		
		public function set position(value:Vector3D):void
		{
			_x = value.x;
			_y = value.y;
			_z = value.z;
			_transformNeedsUpdate = true;
		}
		public function get position():Vector3D
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return new Vector3D(_x, _y, _z);
		}
		
		// Position:
		public function set x(value:Number):void
		{
			_x = value;
			_transformNeedsUpdate = true;
		}
		public function get x():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _x;
		}
		
		public function set y(value:Number):void
		{
			_y = value;
			_transformNeedsUpdate = true;
		}
		public function get y():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _y;
		}
		public function set z(value:Number):void
		{
			_z = value;
			_transformNeedsUpdate = true;
		}
		public function get z():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _z;
		}
		
		// Rotation:
		public function set rotationDegreesX(value:Number):void
		{
			_rotationDegreesX = value;
			_transformNeedsUpdate = true;
		}
		public function get rotationDegreesX():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _rotationDegreesX;
		}
		public function set rotationDegreesY(value:Number):void
		{
			_rotationDegreesY = value;
			_transformNeedsUpdate = true;
		}
		public function get rotationDegreesY():Number{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _rotationDegreesY;
		}
		public function set rotationDegreesZ(value:Number):void
		{
			_rotationDegreesZ = value;
			_transformNeedsUpdate = true;
		}
		public function get rotationDegreesZ():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _rotationDegreesZ;
		}
		
		// Scale:
		public function set scale(vec:Vector3D):void
		{
			_scaleX = vec.x;
			_scaleY = vec.y;
			_scaleZ = vec.z;
			_transformNeedsUpdate = true;
		}
		public function get scale():Vector3D
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return new Vector3D(_scaleX, _scaleY, _scaleZ, 1.0);
		}
		public function set scaleXYZ(value:Number):void
		{
			_scaleX = value;
			_scaleY = value;
			_scaleZ = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleXYZ():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleX; // impossible to determine
			_transformNeedsUpdate = true;
		}
		public function set scaleX(value:Number):void
		{
			_scaleX = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleX():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleX;
		}
		public function set scaleY(value:Number):void
		{
			_scaleY = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleY():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleY;
		}
		public function set scaleZ(value:Number):void
		{
			_scaleZ = value;
			_transformNeedsUpdate = true;
		}
		public function get scaleZ():Number
		{
			if(_valuesNeedUpdate)
				updateValuesFromTransform();
			return _scaleZ;
		}
		
		// Update:
		public function updateTransformFromValues():void
		{
			_transform.identity();
			_transform.appendRotation(_rotationDegreesX, Vector3D.X_AXIS);
			_transform.appendRotation(_rotationDegreesY, Vector3D.Y_AXIS);
			_transform.appendRotation(_rotationDegreesZ, Vector3D.Z_AXIS);
			_transform.appendScale(_scaleX, _scaleY, _scaleZ);
			_transform.appendTranslation(_x, _y, _z);
			_transformNeedsUpdate = false;
		}
		public function updateValuesFromTransform():void
		{
			var d:Vector.<Vector3D> = _transform.decompose();
			var position:Vector3D = d[0];
			_x = position.x;
			_y = position.y;
			_z = position.z;
			var rotation:Vector3D = d[1];
			_rotationDegreesX = rotation.x*RAD_TO_DEG;
			_rotationDegreesY = rotation.y*RAD_TO_DEG;
			_rotationDegreesZ = rotation.z*RAD_TO_DEG;
			var scale:Vector3D = d[2];
			_scaleX = scale.x;
			_scaleY = scale.y;
			_scaleZ = scale.z;
			_valuesNeedUpdate = false;
		}
		
		public function get frontvector():Vector3D
		{
			var vector:Vector3D = new Vector3D(0, 0, 1);
			return transform.deltaTransformVector(vector);
		}
		public function get backvector():Vector3D
		{
			var vector:Vector3D = new Vector3D(0, 0, -1);
			return transform.deltaTransformVector(vector);
		}
		public function get leftvector():Vector3D
		{
			var vector:Vector3D = new Vector3D(-1, 0, 0);
			return transform.deltaTransformVector(vector);
		}
		public function get rightvector():Vector3D
		{
			var vector:Vector3D = new Vector3D(1, 0, 0);
			return transform.deltaTransformVector(vector);
		}
		public function get upvector():Vector3D
		{
			var vector:Vector3D = new Vector3D(0, 1, 0);
			return transform.deltaTransformVector(vector);
		}
		public function get downvector():Vector3D
		{
			var vector:Vector3D = new Vector3D(0, -1, 0);
			return transform.deltaTransformVector(vector);
		}
		
		// Utils:
		// Handy for outputting to a string in a trace() call
		public function pos_string():String
		{
			if (_valuesNeedUpdate)
				updateValuesFromTransform();
			return _x.toFixed(2) + ',' + _y.toFixed(2) + ',' + _z.toFixed(2);
		}
		
		// when you need to use a Vector instead of a Vector3D
		public function get positionVector():Vector.<Number>
		{
			return Vector.<Number>([_x, _y, _z, 1.0]);
		}
		
		public function get inverseTransform():Matrix3D
		{
			_inverseTransform = transform.clone();
			_inverseTransform.invert();
			return _inverseTransform;
		}
		
		public function get rotationTransform():Matrix3D
		{
			var d:Vector.<Vector3D> = transform.decompose();
			d[0] = new Vector3D();
			d[1] = new Vector3D(1, 1, 1);
			var t:Matrix3D = new Matrix3D();
			t.recompose(d);
			return t;
		}
		
		public function get reducedTransform():Matrix3D
		{
			var raw:Vector.<Number> = transform.rawData;
			raw[3] = 0; // Remove translation.
			raw[7] = 0;
			raw[11] = 0;
			raw[15] = 1;
			raw[12] = 0;
			raw[13] = 0;
			raw[14] = 0;
			var reducedTransform:Matrix3D = new Matrix3D();
			reducedTransform.copyRawDataFrom(raw);
			return reducedTransform;
		}
		
		public function get invRotationTransform():Matrix3D
		{
			var t:Matrix3D = rotationTransform;
			t.invert();
			return t;
		}
		
		public function lookAt(target:Vector3D):void
		{
			var position:Vector3D = new Vector3D(_x, _y, _z);
			var yAxis:Vector3D, zAxis:Vector3D, xAxis:Vector3D;
			var upAxis:Vector3D = Vector3D.Y_AXIS;
			zAxis = target.subtract(position);
			zAxis.normalize();
			xAxis = upAxis.crossProduct(zAxis);
			xAxis.normalize();
			yAxis = zAxis.crossProduct(xAxis);
			var raw:Vector.<Number> = new Vector.<Number>(16);
			_transform.copyRawDataTo(raw);
			raw[uint(0)] = _scaleX*xAxis.x;
			raw[uint(1)] = _scaleX*xAxis.y;
			raw[uint(2)] = _scaleX*xAxis.z;
			raw[uint(4)] = _scaleY*yAxis.x;
			raw[uint(5)] = _scaleY*yAxis.y;
			raw[uint(6)] = _scaleY*yAxis.z;
			raw[uint(8)] = _scaleZ*zAxis.x;
			raw[uint(9)] = _scaleZ*zAxis.y;
			raw[uint(10)] = _scaleZ*zAxis.z;
			_transform.copyRawDataFrom(raw);
			var d:Vector.<Vector3D> = _transform.decompose();
			var rotation:Vector3D = d[1];
			_rotationDegreesX = rotation.x * RAD_TO_DEG;
			_rotationDegreesY = rotation.y * RAD_TO_DEG;
			_rotationDegreesZ = rotation.z * RAD_TO_DEG;
			_transformNeedsUpdate = true; // force update
		}
		
		public function follow(thisentity:Molehill_entity):void
		{
			_following = thisentity;
		}
		
		public var matrix:Matrix3D = new Matrix3D();
		public function render(view:Matrix3D,projection:Matrix3D):void
		{
			// only render if these are set
			if (!mesh) return;
			if (!context) return;
			if (!shader) return;
			if (!texture) return;
			//Reset our matrix
			matrix.identity();
			matrix.append(transform);
			if (_following) matrix.append(_following.transform);
			matrix.append(view);
			matrix.append(projection);
			// Set the vertex program register vc0 to our model matrix
			context.setProgramConstantsFromMatrix(
				Context3DProgramType.VERTEX, 0, matrix, true);
			// Set the AGAL program
			context.setProgram(shader);
			// Set the fragment program register ts0 to a texture
			context.setTextureAt(0,texture);
			// position
			context.setVertexBufferAt(0, mesh.positionsBuffer,
				0, Context3DVertexBufferFormat.FLOAT_3);
			// tex coord
			context.setVertexBufferAt(1, mesh.uvBuffer,
				0, Context3DVertexBufferFormat.FLOAT_2);
			// vertex rgba
			context.setVertexBufferAt(2, mesh.colorsBuffer,
				0, Context3DVertexBufferFormat.FLOAT_4);
			context.setBlendFactors(blend_src, blend_dst);
			
			context.setDepthTest(depth_test,depth_test_mode);
			context.setCulling(culling_mode);
			// render it
			context.drawTriangles(mesh.indexBuffer,
				0, mesh.indexBufferCount);
		}
		
		public function clone():Molehill_entity
		{
			if(_transformNeedsUpdate)
				updateTransformFromValues();
			var myclone:Molehill_entity = new Molehill_entity();
			myclone.transform = this.transform.clone();
			myclone.mesh = this.mesh;
			myclone.texture = this.texture;
			myclone.shader = this.shader;
			myclone.vertexBuffer = this.vertexBuffer;
			myclone.indexBuffer = this.indexBuffer;
			myclone.context = this.context;
			myclone.updateValuesFromTransform();
			return myclone;
		}
		
		// move according to the direction we are facing
		public function move_forward(amt:Number):void
		{
			if (_transformNeedsUpdate)
				updateTransformFromValues();
			var v:Vector3D = frontvector;
			v.scaleBy(-amt)
			transform.appendTranslation(v.x, v.y, v.z);
			_valuesNeedUpdate = true;
		}
		
		public function move_backward(amt:Number):void
		{
			if (_transformNeedsUpdate)
				updateTransformFromValues();
			var v:Vector3D = backvector;
			v.scaleBy(-amt)
			transform.appendTranslation(v.x, v.y, v.z);
			_valuesNeedUpdate = true;
		}
		
		public function move_up(amt:Number):void
		{
			if (_transformNeedsUpdate)
				updateTransformFromValues();
			var v:Vector3D = upvector;
			v.scaleBy(amt)
			transform.appendTranslation(v.x, v.y, v.z);
			_valuesNeedUpdate = true;
		}
		public function move_down(amt:Number):void
		{
			if (_transformNeedsUpdate)
				updateTransformFromValues();
			var v:Vector3D = downvector;
			v.scaleBy(amt)
			transform.appendTranslation(v.x, v.y, v.z);
			_valuesNeedUpdate = true;
		}
		public function move_left(amt:Number):void
		{
			if (_transformNeedsUpdate)
				updateTransformFromValues();
			var v:Vector3D = leftvector;
			v.scaleBy(amt)
			transform.appendTranslation(v.x, v.y, v.z);
			_valuesNeedUpdate = true;
		}
		public function move_right(amt:Number):void
		{
			if (_transformNeedsUpdate)
				updateTransformFromValues();
			var v:Vector3D = rightvector;
			v.scaleBy(amt)
			transform.appendTranslation(v.x, v.y, v.z);
			_valuesNeedUpdate = true;
		}
		
		
		
		/**end*/
	}
}